fix: limit concurrent HTTP requests in browser#2304
Conversation
src/core/runtime/dns-browser.js
Outdated
| }) | ||
|
|
||
| self.fetch(url, { mode: 'cors' }) | ||
| _httpQueue.add(() => fetch(url, { mode: 'cors' }) |
There was a problem hiding this comment.
i would use https://github.com/sindresorhus/ky (check this discussion ipfs-inactive/js-ipfs-http-client#1059 (comment))
it will simplify the above lines too and you can specific retries
There was a problem hiding this comment.
Took a stab at ky in 410ac10. Code is much simpler now. Thoughts?
We were using fetch for preload and dns in browser, however addFromURL was handled via custom code on top of node-fetch. I removed custom code around fetch and used ky-universal everywhere for consistency.
All of them benefit from retries provided by ky out of the box.
Question: should we hardcode timeout and retry, or go with defaults?
There was a problem hiding this comment.
we will probably create an ky instance to be used everywhere but for now just leave it hardcoded
19dcf6c to
410ac10
Compare
hugomrdias
left a comment
There was a problem hiding this comment.
i would have preferred a promise first approach like this https://github.com/ipfs/js-ipfs/blob/156125535213836cda58249576f6dbfa2eeac039/src/core/components/resolve.js but i will not block this PR because of it !!
Another suggestion is to use ky.get and ky.post to be more explicit and easier to identify the HTTP operation.
7d82d34 to
070e205
Compare
src/core/runtime/dns-browser.js
Outdated
| hooks: { | ||
| afterResponse: [ | ||
| async response => { | ||
| const query = new URLSearchParams(new URL(response.url).search).toString() |
There was a problem hiding this comment.
the response doesn't have the searchParams already ?
There was a problem hiding this comment.
afaik no, when I checked it only had .url (string)
|
damn ky doesnt support returning a Response from beforeRequest to skip the http request entirely and fetch from cache. |
|
Preload tests fail in browser because preload is enabled in browser context by default and while js-ipfs tests disable preload in tests, interface-ipfs-core does not. This means interface-ipfs-core tests trigger dozens of slow/hanging requests to I believe we should detect js-ipfs is running in test environment ( |
|
This basically is telling us to "really" solve this problem lol, with http2, http cache and making refs endpoint faster Still if you want to do this workaround make sure NODE_ENV is properly set in browsers and electron if not file an issue in aegir and i'll fix it. |
|
We really don't need preload calls in tests other than tests of preload itself. 76ae81c disables preload by default when
Remaining thing to fix is
|
|
The problem was that
As a temporary workaround, 8f41347 fixes the problem in userland by assigning We should solve this upstream somewhow. So far options I see are:
@hugomrdias thoughts? |
|
I would like to test with the new electron 6 before deciding |
|
@hugomrdias same errors in 6.0.0: |
8f41347 to
6fa8f88
Compare
Updates
On
|
This switches to 0.2.x versions of delegate modules which work correctly with js-libp2p + wip [1] fix for js-ipfs that caches DNS records for 1 minute, greatly reducing the HTTP request overhead to remote APIs. [1]: ipfs/js-ipfs#2304
* feat(brave): delegated peers and content routing This enables delegated routers in embedded js-ipfs running in Brave. Coupled with preload, this gives us basic file sharing functionality back, until we have real p2p transport, native DHT etc. * fix: callback-based delgates + DNS caching This switches to 0.2.x versions of delegate modules which work correctly with js-libp2p + wip [1] fix for js-ipfs that caches DNS records for 1 minute, greatly reducing the HTTP request overhead to remote APIs. [1]: ipfs/js-ipfs#2304
|
for reference hugomrdias: ok so we need to find a way to test https js-ipfs/test/core/interface.spec.js Line 67 in fc76875 and skip inside the tests with ipfs-utils/src/env.js do you agree ? lidel: yup |
|
@lidel what's the current status of this PR - still draft? |
|
@alanshaw still a draft. Remaining work here is to figure out I took a break from this PR after my vacation, but will rebase and try to get it ready for review this week. |
6fa8f88 to
25d3144
Compare
|
Just incase this is helpful, you could spin up a server to serve some content via aegir hooks: Line 27 in c67b8a5 Maybe the server accepts a querystring that is the fixture you want it to return? Then in tests you can call it and assert the correct content was added. Won't work with HTTPS though, but only HTTP is better than nothing. |
we need this version for isTest check License: MIT Signed-off-by: Marcin Rataj <lidel@lidel.org>
|
hmm, tests are failing in windows and both browsers and electron. Something is not right here! The |
|
oh boy, this PR is cursed 👀 @alanshaw electron was a random one, but remaining Windows issue was triggered by parallel |
License: MIT Signed-off-by: Marcin Rataj <lidel@lidel.org>
493a00b to
11ab304
Compare
License: MIT Signed-off-by: Marcin Rataj <lidel@lidel.org>
fade339 to
ddd49ce
Compare
|
Resolved conflicts + re-enabled tests for #2379 |
|
This should be ready to merge now. |
achingbrain
left a comment
There was a problem hiding this comment.
A couple of small nits but this is great.
| const addFromURL = async (url, opts = {}) => { | ||
| const res = await ky.get(url) | ||
| const path = decodeURIComponent(new URL(res.url).pathname.split('/').pop()) | ||
| const content = Buffer.from(await res.arrayBuffer()) |
There was a problem hiding this comment.
That day is today. Well, yesterday. Since we merged #2379 you should be able to pass iterables to ipfs.add.
| throw new Error(response.Message) | ||
| } | ||
|
|
||
| module.exports = (fqdn, opts = {}, cb) => { |
There was a problem hiding this comment.
Can this be promise-first please? E.g. make this an async method but export a nodeified version. This will make future refactors to remove callbacks easier.
There was a problem hiding this comment.
I think its already done? the async method is inside:
const resolveDnslink = async (fqdn, opts = {}) => {
...
}
return nodeify(resolveDnslink(fqdn, opts), cb)when we refactor to remove callbacks we just remove the wrapper.
Let me know if this should be handled some other way.
License: MIT Signed-off-by: Marcin Rataj <lidel@lidel.org>
This switch to js-ipfs before PR-2379 ipfs/js-ipfs#2379 switched ipfs.add to ipfs._addAsyncIterator and it broke /api/v0/add exposed by embedded js-ipfs in Brave. It seems old polyfills are no longer enough. Real fix requires more time to investigate, so for now we switch to version before js-ipfs/PR-2379. Used js-ipfs commit is from ipfs/js-ipfs#2304 before it was rebased on top of master after PR-2379, making it the last safe version. Real fix will be tracked in #757
|
I'm encountering this problem in latest |
Can you explain further ? this was a problem in js-ipfs not http-client |
|
@hugomrdias I was playing around with orbitdb + ipfs-http-client in web browser (connected to my local go-ipfs). I found that when I subscribe to a lot of datastores, orbitdb's periodic fetches / pubsub on each datastore effectively use up the concurrent HTTP request limit and stall the entire pipeline of requests, just like what's shown in the screenshot in the PR. This behavior seems to be a result from https://github.com/ipfs-shipyard/ipfs-pubsub-1on1. When I saw this PR, I thought this might be something that addresses my issue, but you are right - I missed that this PR was for js-ipfs. It wouldn't make much sense for ipfs-http-client in fact. |
|
can you make another issue with further info maybe a repo we can clone please ? |
|
I've decided to drop OrbitDB for now, so maybe won't see the issue anymore. But if I manage to reproduce it in a consistent manner will log a separate issue like you suggested. Thanks! |




This PR solves request floods like this:
It adds a limit of concurrent HTTP requests sent to remote API
by dns and preload calls when running in web browser contexts.
Browsers limit connections per host (~6). This change mitigates the problem of expensive and long running calls of one type exhausting connection pool for other uses.
This is similar to problems described in libp2p/js-libp2p-delegated-content-routing#12
This PR additionally limits the number of DNS lookup calls by introducing time-bound cache with eviction rules following what browser already do.
TODO
/api/v0/dnsin browserprocess.env.NODE_ENV === 'test'addFromURLtestscc ipfs/ipfs-companion#716